﻿/*************************************************************************************

   Extended WPF Toolkit

   Copyright (C) 2007-2013 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://wpftoolkit.codeplex.com/license 

   For more features, controls, and fast professional support,
   pick up the Plus Edition at http://xceed.com/wpf_toolkit

   Stay informed: follow @datagrid on Twitter or Like http://facebook.com/datagrids

  ***********************************************************************************/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Runtime.InteropServices;
using System.Windows.Interop;
using System.Windows.Input;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using Xceed.Wpf.AvalonDock.Layout;
using System.Diagnostics;
using System.Windows.Documents;
using Xceed.Wpf.AvalonDock.Themes;
using Microsoft.Windows.Shell;

namespace Xceed.Wpf.AvalonDock.Controls
{
    public abstract class LayoutFloatingWindowControl : Window, ILayoutControl
    {
      private ResourceDictionary currentThemeResourceDictionary; // = null

        static LayoutFloatingWindowControl()
        {
            LayoutFloatingWindowControl.ContentProperty.OverrideMetadata(typeof(LayoutFloatingWindowControl), new FrameworkPropertyMetadata(null, null, new CoerceValueCallback(CoerceContentValue)));
            AllowsTransparencyProperty.OverrideMetadata(typeof(LayoutFloatingWindowControl), new FrameworkPropertyMetadata(false));
            ShowInTaskbarProperty.OverrideMetadata(typeof(LayoutFloatingWindowControl), new FrameworkPropertyMetadata(false));
        }

        static object CoerceContentValue( DependencyObject sender, object content )
        {
          return new FloatingWindowContentHost( sender as LayoutFloatingWindowControl ) { Content = content as UIElement };
        }

        protected class FloatingWindowContentHost : HwndHost
            {
                LayoutFloatingWindowControl _owner;
                public FloatingWindowContentHost(LayoutFloatingWindowControl owner)
                {
                    _owner = owner;
                    var manager = _owner.Model.Root.Manager;
                }


                HwndSource _wpfContentHost = null;
                Border _rootPresenter = null;
                DockingManager _manager = null;

                protected override System.Runtime.InteropServices.HandleRef BuildWindowCore(System.Runtime.InteropServices.HandleRef hwndParent)
                {
                    _wpfContentHost = new HwndSource(new HwndSourceParameters()
                    {
                        ParentWindow = hwndParent.Handle,
                        WindowStyle = Win32Helper.WS_CHILD | Win32Helper.WS_VISIBLE | Win32Helper.WS_CLIPSIBLINGS | Win32Helper.WS_CLIPCHILDREN,
                        Width = 1,
                        Height = 1
                    });

                    _rootPresenter = new Border() { Child = new AdornerDecorator() { Child = Content }, Focusable = true };
                    _rootPresenter.SetBinding(Border.BackgroundProperty, new Binding("Background") { Source = _owner });
                    _wpfContentHost.RootVisual = _rootPresenter;
                    _wpfContentHost.SizeToContent = SizeToContent.Manual;
                    _manager = _owner.Model.Root.Manager;
                    _manager.InternalAddLogicalChild(_rootPresenter);

                    return new HandleRef(this, _wpfContentHost.Handle);
                }


                protected override void DestroyWindowCore(HandleRef hwnd)
                {
                    _manager.InternalRemoveLogicalChild(_rootPresenter);
                    if (_wpfContentHost != null)
                    {
                        _wpfContentHost.Dispose();
                        _wpfContentHost = null;
                    }
                }

                public Visual RootVisual
                {
                    get { return _rootPresenter; }
                }

                protected override Size MeasureOverride(Size constraint)
                {
                    if (Content == null)
                        return base.MeasureOverride(constraint);

                    Content.Measure(constraint);
                    return Content.DesiredSize;
                }

                #region Content

                /// <summary>
                /// Content Dependency Property
                /// </summary>
                public static readonly DependencyProperty ContentProperty =
                    DependencyProperty.Register("Content", typeof(UIElement), typeof(FloatingWindowContentHost),
                        new FrameworkPropertyMetadata((UIElement)null,
                            new PropertyChangedCallback(OnContentChanged)));

                /// <summary>
                /// Gets or sets the Content property.  This dependency property 
                /// indicates ....
                /// </summary>
                public UIElement Content
                {
                    get { return (UIElement)GetValue(ContentProperty); }
                    set { SetValue(ContentProperty, value); }
                }

                /// <summary>
                /// Handles changes to the Content property.
                /// </summary>
                private static void OnContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
                {
                    ((FloatingWindowContentHost)d).OnContentChanged(e);
                }

                /// <summary>
                /// Provides derived classes an opportunity to handle changes to the Content property.
                /// </summary>
                protected virtual void OnContentChanged(DependencyPropertyChangedEventArgs e)
                {
                    if (_rootPresenter != null)
                        _rootPresenter.Child = Content;
                }

                #endregion
            }

        ILayoutElement _model;

        protected LayoutFloatingWindowControl(ILayoutElement model)
        {
            this.Loaded += new RoutedEventHandler(OnLoaded);
            this.Unloaded += new RoutedEventHandler(OnUnloaded);
            _model = model;
            UpdateThemeResources();
        }

        internal virtual void UpdateThemeResources(Theme oldTheme = null)
        {
            if (oldTheme != null)
            {
              if( oldTheme is DictionaryTheme )
              {
                if( currentThemeResourceDictionary != null )
                {
                  Resources.MergedDictionaries.Remove( currentThemeResourceDictionary );
                  currentThemeResourceDictionary = null;
                }
              }
              else
              {
                var resourceDictionaryToRemove =
                    Resources.MergedDictionaries.FirstOrDefault( r => r.Source == oldTheme.GetResourceUri() );
                if( resourceDictionaryToRemove != null )
                  Resources.MergedDictionaries.Remove(
                      resourceDictionaryToRemove );
              }
            }

            var manager = _model.Root.Manager;
            if (manager.Theme != null)
            {
              if( manager.Theme is DictionaryTheme )
              {
                currentThemeResourceDictionary = ( ( DictionaryTheme )manager.Theme ).ThemeResourceDictionary;
                Resources.MergedDictionaries.Add( currentThemeResourceDictionary );
              }
              else
              {
                Resources.MergedDictionaries.Add(new ResourceDictionary() { Source = manager.Theme.GetResourceUri() });
              }
            }
        }

        protected override void OnClosed(EventArgs e)
        {
            if (Content != null)
            {
                var host = Content as FloatingWindowContentHost;
                if( host != null )
                {
                  host.Dispose();
                }

                if (_hwndSrc != null)
                {
                    _hwndSrc.RemoveHook(_hwndSrcHook);
                    _hwndSrc.Dispose();
                    _hwndSrc = null;
                }
            }

            base.OnClosed(e);
        }

        bool _attachDrag = false;
        internal void AttachDrag(bool onActivated = true)
        {
            if (onActivated)
            {
                _attachDrag = true;
                this.Activated += new EventHandler(OnActivated);
            }
            else
            {
                IntPtr windowHandle = new WindowInteropHelper(this).Handle;
                IntPtr lParam = new IntPtr(((int)Left & (int)0xFFFF) | (((int)Top) << 16));
                Win32Helper.SendMessage(windowHandle, Win32Helper.WM_NCLBUTTONDOWN, new IntPtr(Win32Helper.HT_CAPTION), lParam);
            }
        }

        HwndSource _hwndSrc;
        HwndSourceHook _hwndSrcHook;

        void OnLoaded(object sender, RoutedEventArgs e)
        {
            this.Loaded -= new RoutedEventHandler(OnLoaded);

            this.SetParentToMainWindowOf(Model.Root.Manager);

            _hwndSrc = HwndSource.FromDependencyObject(this) as HwndSource;
            _hwndSrcHook = new HwndSourceHook(FilterMessage);
            _hwndSrc.AddHook(_hwndSrcHook);

           // Restore maximize state
           var maximized = Model.Descendents().OfType<ILayoutElementForFloatingWindow>().Any(l => l.IsMaximized);
           UpdateMaximizedState(maximized);
        }

        void OnUnloaded(object sender, RoutedEventArgs e)
        {
            this.Unloaded -= new RoutedEventHandler(OnUnloaded);

            if (_hwndSrc != null)
            {
                _hwndSrc.RemoveHook(_hwndSrcHook);
                InternalClose();
            }
        }

        void OnActivated(object sender, EventArgs e)
        {
            this.Activated -= new EventHandler(OnActivated);

            if (_attachDrag && Mouse.LeftButton == MouseButtonState.Pressed)
            {
                IntPtr windowHandle = new WindowInteropHelper(this).Handle;
                var mousePosition = this.PointToScreenDPI(Mouse.GetPosition(this));
                var clientArea = Win32Helper.GetClientRect(windowHandle);
                var windowArea = Win32Helper.GetWindowRect(windowHandle);

                Left = mousePosition.X - windowArea.Width / 2.0;
                Top = mousePosition.Y - (windowArea.Height - clientArea.Height) / 2.0;
                _attachDrag = false;

                IntPtr lParam = new IntPtr(((int)mousePosition.X & (int)0xFFFF) | (((int)mousePosition.Y) << 16));
                Win32Helper.SendMessage(windowHandle, Win32Helper.WM_NCLBUTTONDOWN, new IntPtr(Win32Helper.HT_CAPTION), lParam);
            }
        }


        protected override void OnInitialized(EventArgs e)
        {
            CommandBindings.Add(new CommandBinding(Microsoft.Windows.Shell.SystemCommands.CloseWindowCommand,
                new ExecutedRoutedEventHandler((s, args) => Microsoft.Windows.Shell.SystemCommands.CloseWindow((Window)args.Parameter))));
            CommandBindings.Add(new CommandBinding(Microsoft.Windows.Shell.SystemCommands.MaximizeWindowCommand,
                new ExecutedRoutedEventHandler((s, args) => Microsoft.Windows.Shell.SystemCommands.MaximizeWindow((Window)args.Parameter))));
            CommandBindings.Add(new CommandBinding(Microsoft.Windows.Shell.SystemCommands.MinimizeWindowCommand,
                new ExecutedRoutedEventHandler((s, args) => Microsoft.Windows.Shell.SystemCommands.MinimizeWindow((Window)args.Parameter))));
            CommandBindings.Add(new CommandBinding(Microsoft.Windows.Shell.SystemCommands.RestoreWindowCommand,
                new ExecutedRoutedEventHandler((s, args) => Microsoft.Windows.Shell.SystemCommands.RestoreWindow((Window)args.Parameter))));
            //Debug.Assert(this.Owner != null);
            base.OnInitialized(e);
        }

        public abstract ILayoutElement Model { get; }


        #region IsDragging

        /// <summary>
        /// IsDragging Read-Only Dependency Property
        /// </summary>
        private static readonly DependencyPropertyKey IsDraggingPropertyKey
            = DependencyProperty.RegisterReadOnly("IsDragging", typeof(bool), typeof(LayoutFloatingWindowControl),
                new FrameworkPropertyMetadata((bool)false,
                    new PropertyChangedCallback(OnIsDraggingChanged)));

        public static readonly DependencyProperty IsDraggingProperty
            = IsDraggingPropertyKey.DependencyProperty;

        /// <summary>
        /// Gets the IsDragging property.  This dependency property 
        /// indicates that this floating window is being dragged.
        /// </summary>
        public bool IsDragging
        {
            get { return (bool)GetValue(IsDraggingProperty); }
        }

        /// <summary>
        /// Provides a secure method for setting the IsDragging property.  
        /// This dependency property indicates that this floating window is being dragged.
        /// </summary>
        /// <param name="value">The new value for the property.</param>
        protected void SetIsDragging(bool value)
        {
            SetValue(IsDraggingPropertyKey, value);
        }

        /// <summary>
        /// Handles changes to the IsDragging property.
        /// </summary>
        private static void OnIsDraggingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((LayoutFloatingWindowControl)d).OnIsDraggingChanged(e);
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the IsDragging property.
        /// </summary>
        protected virtual void OnIsDraggingChanged(DependencyPropertyChangedEventArgs e)
        {
          if( ( bool )e.NewValue )
          {
            CaptureMouse();
          }
          else
          {
            ReleaseMouseCapture();
          }
        }

        #endregion

        DragService _dragService = null;

        void UpdatePositionAndSizeOfPanes()
        {
            foreach (var posElement in Model.Descendents().OfType<ILayoutElementForFloatingWindow>())
            {
                posElement.FloatingLeft = Left;
                posElement.FloatingTop = Top;
                posElement.FloatingWidth = Width;
                posElement.FloatingHeight = Height;
            }
        }

        void UpdateMaximizedState( bool isMaximized )
        {
          foreach( var posElement in Model.Descendents().OfType<ILayoutElementForFloatingWindow>() )
          {
            posElement.IsMaximized = isMaximized;
          }
          IsMaximized = isMaximized;
          WindowState = isMaximized ? WindowState.Maximized : WindowState.Normal;
        }


        protected virtual IntPtr FilterMessage(
            IntPtr hwnd,
            int msg,
            IntPtr wParam,
            IntPtr lParam,
            ref bool handled
            )
        {
            handled = false;

            switch (msg)
            {
                case Win32Helper.WM_ACTIVATE:
                    if (((int)wParam & 0xFFFF) == Win32Helper.WA_INACTIVE)
                    {
                        if (lParam == this.GetParentWindowHandle())
                        {
                            Win32Helper.SetActiveWindow(_hwndSrc.Handle);
                            handled = true;
                        }
                    }
                    break;
                case Win32Helper.WM_EXITSIZEMOVE:
                    UpdatePositionAndSizeOfPanes();

                    if (_dragService != null)
                    {
                        bool dropFlag;
                        var mousePosition = this.TransformToDeviceDPI(Win32Helper.GetMousePosition());
                        _dragService.Drop(mousePosition, out dropFlag);
                        _dragService = null;
                        SetIsDragging(false);

                        if (dropFlag)
                            InternalClose();
                    }

                    break;
                case Win32Helper.WM_MOVING:
                    {
                        UpdateDragPosition();
                    }
                    break;
                case Win32Helper.WM_LBUTTONUP: //set as handled right button click on title area (after showing context menu)
                    if (_dragService != null && Mouse.LeftButton == MouseButtonState.Released)
                    {
                        _dragService.Abort();
                        _dragService = null;
                        SetIsDragging(false);
                    }
                    break;
              case Win32Helper.WM_SYSCOMMAND:
                    int command = (int)wParam & 0xFFF0;
                    if (command == Win32Helper.SC_MAXIMIZE || command == Win32Helper.SC_RESTORE)
                    {
                      UpdateMaximizedState( command == Win32Helper.SC_MAXIMIZE );
                    }
                    break;
            }



            return IntPtr.Zero;
        }

        private void UpdateDragPosition()
        {
            if (_dragService == null)
            {
                _dragService = new DragService(this);
                SetIsDragging(true);
            }

            var mousePosition = this.TransformToDeviceDPI(Win32Helper.GetMousePosition());
            _dragService.UpdateMouseLocation(mousePosition);
        }

        bool _internalCloseFlag = false;

        internal void InternalClose()
        {
            _internalCloseFlag = true;
            Close();
        }


        protected bool CloseInitiatedByUser
        {
            get { return !_internalCloseFlag; }
        }

        internal bool KeepContentVisibleOnClose
        {
            get;
            set;
        }

        protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
        {
            base.OnMouseLeftButtonUp(e);
        }

        #region IsMaximized

        /// <summary>
        /// IsMaximized Dependency Property
        /// </summary>
        public static readonly DependencyProperty IsMaximizedProperty
              = DependencyProperty.Register( "IsMaximized", typeof( bool ), typeof( LayoutFloatingWindowControl ),
                  new FrameworkPropertyMetadata( (bool)false ) );

        /// <summary>
        /// Gets/sets the IsMaximized property.  This dependency property 
        /// indicates if the window is maximized.
        /// </summary>
        public bool IsMaximized
        {
            get { return (bool)GetValue(IsMaximizedProperty); }
            private set
              {
                  SetValue(IsMaximizedProperty, value);
                  UpdatePositionAndSizeOfPanes();
              }
        }

        /// <summary>
        /// Provides a secure method for setting the IsMaximized property.  
        /// This dependency property indicates if the window is maximized.
        /// </summary>
        /// <param name="value">The new value for the property.</param>

        protected override void OnStateChanged(EventArgs e)
        {
            //Windows sometimes send unwanted state changes (when minimizing application for instance)
            //We force internal state to be used
            WindowState = IsMaximized ? WindowState.Maximized : WindowState.Normal;
            base.OnStateChanged(e);
        }

        #endregion







    }
}
